package com.gitee.wsl.doc.prosemirror.transform.step

import com.gitee.wsl.doc.prosemirror.exception.RangeError
import com.gitee.wsl.doc.prosemirror.model.Schema
import com.gitee.wsl.doc.prosemirror.model.fragment.Slice
import com.gitee.wsl.doc.prosemirror.model.fragment.ext.mapFragment
import com.gitee.wsl.doc.prosemirror.model.mark.Mark
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.transform.map.Mappable
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlin.Int
import kotlin.math.max
import kotlin.math.min

// Add a mark to all inline content between two positions.
class AddMarkStep(
    // Create a mark step.
    // The start of the marked range.
    override val from: Int,
    // The end of the marked range.
    override var to: Int,
    // The mark to add.
    val mark: Mark
) : Step() {

    override fun apply(doc: Node): StepResult {
        val oldSlice = doc.slice(this.from, this.to)
        val resolvedFrom = doc.resolve(this.from)
        val nodeParent = resolvedFrom.node(resolvedFrom.sharedDepth(this.to))
        val slice = Slice(
            mapFragment(oldSlice.content, { node: Node, parent: Node, i: Int ->
                if (!node.isAtom || !parent.type.allowsMarkType(this.mark.type)) {
                    node
                } else {
                    node.mark(this.mark.addToSet(node.marks))
                }
            }, nodeParent),
            oldSlice.openStart,
            oldSlice.openEnd
        )
        return StepResult.fromReplace(doc, this.from, this.to, slice)
    }

    override fun invert(doc: Node): Step {
        return RemoveMarkStep(this.from, this.to, this.mark)
    }

    override fun map(mapping: Mappable): Step? {
        val from = mapping.mapResult(this.from, 1)
        val to = mapping.mapResult(this.to, -1)
        if (from.deleted && to.deleted || from.pos >= to.pos) return null
        return AddMarkStep(from.pos, to.pos, this.mark)
    }

    @Suppress("ComplexCondition")
    override fun merge(other: Step): Step? {
        return if (other is AddMarkStep &&
            other.mark == this.mark &&
            this.from <= other.to &&
            this.to >= other.from
        ) {
            AddMarkStep(
                min(this.from, other.from),
                max(this.to, other.to),
                this.mark
            )
        } else {
            null
        }
    }

    override fun toJSON(): JsonObject {
        return buildJsonObject {
            put("stepType", "addMark")
            put("mark", mark.toJSON())
            put("from", from)
            put("to", to)
        }
    }

    companion object : StepJsonParser<AddMarkStep> {
        init {
            jsonID("addMark", this)
        }

        override fun fromJSON(schema: Schema, json: JsonObject): AddMarkStep {
            val from: Int? = json["from"]?.jsonPrimitive?.int
            val to: Int? = json["to"]?.jsonPrimitive?.int
            if (from == null || to == null) {
                throw RangeError("Invalid input for AddMarkStep.fromJSON")
            }
            return AddMarkStep(from, to, schema.markFromJSON(json["mark"]?.jsonObject))
        }
    }
}

fun Step.Companion.addMark(
    // Create a mark step.
    // The start of the marked range.
     from: Int,
    // The end of the marked range.
    to: Int,
    // The mark to add.
    mark: Mark
) = AddMarkStep(from,to,mark)